import java.rmi.Naming;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.Map;


public class Server extends UnicastRemoteObject implements Server_itf{
	private static final int RMI_REG_PORT = 1099;
	private static final String name = "Coordinator";
	
		
	private String url;
	
	private static int nextFreeId = 0;
	private Map<String, Integer> dns = new HashMap<String, Integer>();
	private Map<Integer, ServerObject> obs = new HashMap<Integer, ServerObject>();
	

	
	public static void main(String[] args) {
		
		try {
			Server_itf s = new Server();
		} catch (RemoteException e) {
			System.err.println("Something strange happened! Couldn't create a new server.");
			e.printStackTrace();
		}
		/*
		System.out.print("Input to shutdown. "); System.out.flush();
		InputStreamReader isr = new InputStreamReader(System.in);
		try{
			isr.read();
		}catch(IOException e){
			e.printStackTrace();
		}
		System.exit(0);
		//*/
	}

	public Server() throws RemoteException{
		try{
			url = "//" + InetAddress.getLocalHost().getHostName() + ':' + RMI_REG_PORT + '/' + name;

			try{
				Naming.rebind(url, this);
			}catch (MalformedURLException e) {
				System.err.println("Wrong URL form!");
				e.printStackTrace();
			}catch (RemoteException e) {
				System.err.println("Couldn't rebind the server name!");
				e.printStackTrace();
			}
			
		}catch (UnknownHostException e) {
			System.err.println("Something went terribly wrong! Couldn't resolve localhost name.");
			e.printStackTrace();
		}
	}
	


	/**
	 * Given a SharedObject's name, looks up its id.
	 * @param name	the name of the SharedObject to be looked up
	 * @return	the id of the SharedObject, or -1, if such a SharedObject
	 * doesn't exist.
	 */
	public Descriptor lookup(String name) throws RemoteException {
		if(dns.containsKey(name)) {
			int id = dns.get(name);
			System.out.println("Object found: " + name + ", " + id);
			return obs.get(id).getDescriptor();
		}else
			return null;
	}
	
	/**
	 * Registers a name for an id of a SharedObject.
	 * If there has already been a mapping for the key, removes
	 * the object with the given id from the server. Since there
	 * was already a mapping for the given name, it is not changed.
	 * @param name	the name to be given to the new mapping
	 * @param id	the id of the SharedObject
	 * 
	 * @throws NameAlreadyExistsException	if name was already bound to
	 * something.
	 */
	public void register(String name, int id) throws RemoteException{
		synchronized(dns){	//only one thread registers at a time.
			if(!dns.containsKey(name)) {
				dns.put(name, id);
				System.out.println("New object registered : " + name + ", " + id + ".");
			}
			else{
				obs.remove(id);	//TODO: reaaaaly nasty PROTOCOL
				//obs.put(id, obs.get(dns.get(name)));	//an alternative solution to the double-register problem.
				throw new NameAlreadyExistsException();
			}
		}
	}

	/**
	 * Given a serializable object, adds it to the local registry and gives it an id. Returns the id.
	 * @param o	a serializable object to be added to the registry
	 * @return	 the id given to the new object
	 */
	public int create(Object o) throws RemoteException {
		int freeId;
		synchronized(obs){	//only one thread gets the free id at a time
			freeId = nextFreeId++;
		}
		
		obs.put(freeId, new ServerObject_Impl(freeId, o));
		return freeId;
	}
	
	/**
	 * Acquires the lock of the object with the given id for reading.
	 * @param id	the id of the object whose read-lock is being acquired
	 * @param client	the client acquiring the lock
	 * @return	the object whose read-lock was acquired
	 * 
	 * @throws NoSuchObjectException	if no object was found for the given id.
	 */
	public Object lock_read(int id, Client_itf client) throws RemoteException {
		ServerObject ob = obs.get(id);
		if(ob == null)
			throw new NoSuchObjectException();
		else
			return ob.lock_read(client);
	}

	/**
	 * Acquires the lock of the object with the given id for writing.
	 * @param id	the id of the object whose write-lock is being acquired
	 * @param client	the client acquiring the lock
	 * @return	the object whose write-lock was acquired
	 * 
	 * @throws NoSuchObjectException	if no object was found for the given id.
	 */
	public Object lock_write(int id, Client_itf client) throws RemoteException {
		ServerObject ob = obs.get(id);
		if(ob == null)
			throw new NoSuchObjectException();
		else
			return ob.lock_write(client);
	}
}
